StringSQLAppender.java

package org.codefilarete.stalactite.query.builder;

import javax.annotation.Nullable;

import org.codefilarete.stalactite.query.model.Fromable;
import org.codefilarete.stalactite.query.model.Placeholder;
import org.codefilarete.stalactite.query.model.QueryStatement.PseudoColumn;
import org.codefilarete.stalactite.query.model.Selectable;
import org.codefilarete.stalactite.query.model.ValuedVariable;
import org.codefilarete.stalactite.sql.ddl.DDLAppender;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.tool.StringAppender;

/**
 * A basic SQL appender to a {@link StringAppender}.
 * Values are rawly included in the final {@link StringAppender}, therefore this class must be use with
 * caution due to potential SQL Injection.
 */
public class StringSQLAppender implements SQLAppender {
	
	private final DDLAppender delegate;
	
	public StringSQLAppender(DMLNameProvider dmlNameProvider) {
		this.delegate = new QualifiedNameDDLAppender(dmlNameProvider);
	}
	
	private StringSQLAppender(DDLAppender delegate) {
		this.delegate = delegate;
	}
	
	public DDLAppender getDelegate() {
		return delegate;
	}
	
	@Override
	public StringSQLAppender cat(String s, String... ss) {
		delegate.cat(s).cat(ss);
		return this;
	}
	
	@Override
	public <V> StringSQLAppender catValue(@Nullable Selectable<V> column, Object value) {
		return catValue(value);
	}
	
	@Override
	public StringSQLAppender catValue(Object value) {
		if (value instanceof ValuedVariable<?>) {
			value = ((ValuedVariable) value).getValue();
		}
		if (value instanceof CharSequence) {
			// specialized case to escape single quotes
			delegate.cat("'", value.toString().replace("'", "''"), "'");
		} else {
			delegate.cat(value);
		}
		return this;
	}
	
	@Override
	public StringSQLAppender catColumn(Selectable<?> column) {
		// Columns are simply appended (no binder needed nor index increment)
		delegate.cat(column);
		return this;
	}
	
	@Override
	public SQLAppender catTable(Fromable table) {
		delegate.cat(table);
		return this;
	}
	
	@Override
	public SQLAppender removeLastChars(int length) {
		delegate.cutTail(length);
		return this;
	}
	
	@Override
	public String getSQL() {
		return delegate.toString();
	}
	
	@Override
	public SubSQLAppender newSubPart(DMLNameProvider dmlNameProvider) {
		return new DefaultSubSQLAppender(new StringSQLAppender(new QualifiedNameDDLAppender(this.delegate.getAppender(), dmlNameProvider))) {
			@Override
			public SQLAppender close() {
				// nothing special
				return StringSQLAppender.this;
			}
		};
	}
	
	private static class QualifiedNameDDLAppender extends DDLAppender {
		
		public QualifiedNameDDLAppender(DMLNameProvider dmlNameProvider) {
			super(dmlNameProvider);
		}
		
		public QualifiedNameDDLAppender(StringBuilder delegate, DMLNameProvider dmlNameProvider) {
			super(delegate, dmlNameProvider);
		}
		
		@Override
		public StringAppender cat(Object o) {
			if (o instanceof Column || o instanceof PseudoColumn) {
				return super.cat(dmlNameProvider.getName((Selectable<?>) o));
			} else if (o instanceof Selectable) {
				return super.cat(((Selectable<?>) o).getExpression());
			} else if (o instanceof Placeholder) {
				return super.cat(":" + ((Placeholder<?, ?>) o).getName());
			} else {
				return super.cat(o);
			}
		}
	}
}